/* run2_gpl -- Core routines for the run2 wrapper which are GPL
 * Copyright (C) 1998,2009  Charles S. Wilson
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#ifdef _MSC_VER
# define _CRT_SECURE_NO_DEPRECATE 1
#endif

#if HAVE_CONFIG_H
# include <config.h>
#endif

#define WIN32

/* want windows XP or above */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define NOMINMAX
#include <windows.h>
#include <shellapi.h>

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>
#include <winuser.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#include "lib/util.h"
#include "lib/confdata.h"
#include "run2_gpl.h"



/* Copy cygwin environment variables to the Windows environment if they're not
 * already there. */
void
run2_setup_win_environ (void)
{
  char **envp = environ;
  char *var, *val;
  char curval[2];

  while (envp && *envp)
    {
      var = strdup (*envp++);
      val = strchr (var, '=');
      if (val != NULL)
        {
          *val++ = '\0';

          if (GetEnvironmentVariable (var, curval, 2) == 0 &&
              GetLastError () == ERROR_ENVVAR_NOT_FOUND)
            {
              SetEnvironmentVariable (var, val);
            }
        }
      free (var);
    }
}

BOOL
run2_have_console (void)
{
  /* Note: we do it this way instead of using GetConsoleWindow()
   * because we want it to work on < w2k.
   */
  HANDLE hConOut;
  SECURITY_ATTRIBUTES  sa;
  CONSOLE_SCREEN_BUFFER_INFO buffInfo;

  BOOL retval = FALSE;
  sa.nLength = sizeof(sa);
  sa.bInheritHandle = TRUE;
  sa.lpSecurityDescriptor = NULL;
  hConOut = CreateFile ("CONOUT$", GENERIC_WRITE | GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
                        OPEN_EXISTING, 0, 0 );
  if (hConOut != INVALID_HANDLE_VALUE)
    {
      if (GetConsoleScreenBufferInfo (hConOut,&buffInfo))
        {
          retval = TRUE;
        }
      CloseHandle (hConOut);
    }
  debugMsg (2, "have console? %s", (retval ? "yes" : "no"));
  return retval;
}

BOOL
run2_target_is_gui (const char* target_path)
{
  char p, e;
  BOOL retval = FALSE;
  DWORD_PTR d =
    SHGetFileInfoA(target_path,    /* LPCSTR pszPath         */
                   0,              /* DWORD dwFileAttributes */
                   NULL,           /* SHFILEINFO *psfi       */
                   0,              /* UINT cbFileInfo        */
                   SHGFI_EXETYPE); /* UINT uFlags            */

  p = LOBYTE (LOWORD (d));
  e = HIBYTE (LOWORD (d));
  retval = (((p=='P') || (p=='N')) && (e=='E')) && (HIWORD(d) != 0);
  debugMsg (2, "target is gui? %s", (retval ? "yes" : "no"));
  return retval;
}

BOOL
run2_setup_invisible_console ()
{
  HWINSTA h, horig;
  USEROBJECTFLAGS oi;
  DWORD len;
  BOOL b = FALSE;
  HMODULE lib = NULL;
  HWINSTA WINAPI (*GetProcessWindowStationFP) (void) = NULL;
  BOOL WINAPI (*GetUserObjectInformationFP) (HANDLE, int, PVOID, DWORD,
                                             PDWORD) = NULL;
  HWINSTA WINAPI (*CreateWindowStationFP) (LPCWSTR, DWORD, DWORD,
                                           LPSECURITY_ATTRIBUTES) = NULL;
  BOOL WINAPI (*SetProcessWindowStationFP) (HWINSTA) = NULL;
  BOOL WINAPI (*CloseWindowStationFP) (HWINSTA) = NULL;

  debugMsg (2, "Setting up invisible console using separate WindowStation");

  /* paranoia */
  if (run2_have_console ())
    {
      debugMsg (2, "Already have a console; not setting up another one.");
      return TRUE;
    }


  /* First, set up function pointers */
  if ((lib = LoadLibrary ("user32.dll")))
    {
      GetProcessWindowStationFP = (HWINSTA WINAPI (*)(void))
        GetProcAddress (lib, "GetProcessWindowStation");
      GetUserObjectInformationFP = (BOOL WINAPI (*)(HANDLE, int, PVOID, DWORD, PDWORD))
        GetProcAddress (lib, "GetUserObjectInformationW");      /* ugly! */
      CreateWindowStationFP = (HWINSTA WINAPI (*)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES))
        GetProcAddress (lib, "CreateWindowStationW");  /* ugly */
      SetProcessWindowStationFP = (BOOL WINAPI (*)(HWINSTA))
        GetProcAddress (lib, "SetProcessWindowStation");
      CloseWindowStationFP = (BOOL WINAPI (*)(HWINSTA))
        GetProcAddress (lib, "CloseWindowStation");

      if (GetProcessWindowStationFP &&
          GetUserObjectInformationFP &&
          CreateWindowStationFP &&
          SetProcessWindowStationFP &&
          CloseWindowStationFP)
        {
          /* Then, do the work */
          FreeConsole ();
          h = horig = (*GetProcessWindowStationFP) ();
          if (!horig
              || !(*GetUserObjectInformationFP) (horig, UOI_FLAGS, &oi,
                                                 sizeof (oi), &len)
              || !(oi.dwFlags & WSF_VISIBLE))
            {
              b = AllocConsole ();
            }
          else
            {
              h =
                (*CreateWindowStationFP) (NULL, 0, STANDARD_RIGHTS_READ,
                                          NULL);
              if (h)
                {
                  b = (*SetProcessWindowStationFP) (h);
                }
              b = AllocConsole ();
              if (horig && h && h != horig
                  && (*SetProcessWindowStationFP) (horig))
                {
                  (*CloseWindowStationFP) (h);
                }
            }
          debugMsg (2, "Succeeded in setting up console on separate Window Station? %s", (b ? "yes" : "no"));
          return b;
        }
    }
  /* otherwise, fail */
  debugMsg (2, "Failed to set up console on separate Window Station");
  return FALSE;
}

/* returns FALSE only on error conditions (not impl) */
BOOL
run2_configure_startupinfo (STARTUPINFO * psi, BOOL bHaveConsole,
                            BOOL bForceUsingPipes, BOOL * bUsingPipes,
                            HANDLE * hpToChild, HANDLE * hpFromChild,
                            HANDLE * hpToParent, HANDLE * hpFromParent)
{
  SECURITY_ATTRIBUTES handle_attrs;
  HANDLE hpStdInput[2];
  HANDLE hpStdOutput[2];

  debugMsg (2, "Configuring start info for child process");

  ZeroMemory (psi, sizeof (STARTUPINFO));
  psi->cb = sizeof (STARTUPINFO);
  psi->hStdInput  = GetStdHandle (STD_INPUT_HANDLE);
  psi->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
  psi->hStdError  = GetStdHandle (STD_ERROR_HANDLE);
  psi->dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
  psi->wShowWindow = SW_HIDE;

  /* If we have a console and have not requested pipes,
   * ensure that the child stdio handles are properly
   * connected.
   */
  if (!bForceUsingPipes && bHaveConsole)
    {
      SECURITY_ATTRIBUTES  sa;

      sa.nLength = sizeof(sa);
      sa.bInheritHandle = TRUE;
      sa.lpSecurityDescriptor = NULL;

      *bUsingPipes = FALSE;
      psi->hStdInput   = CreateFile( "CONIN$", GENERIC_WRITE | GENERIC_READ,
                         FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
                         OPEN_EXISTING, 0, 0 );
      psi->hStdOutput  = CreateFile( "CONOUT$", GENERIC_WRITE | GENERIC_READ,
                         FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
                         OPEN_EXISTING, 0, 0 );
      psi->hStdError   = CreateFile( "CONOUT$", GENERIC_WRITE | GENERIC_READ,
                         FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
                         OPEN_EXISTING, 0, 0 );
      debugMsg (2, "Have a console, and not requesting pipes: connecting child stdio to console");
      return TRUE;
    }

  /* otherwise, set up pipes */
  *bUsingPipes = TRUE;

  handle_attrs.nLength = sizeof (SECURITY_ATTRIBUTES);
  handle_attrs.bInheritHandle = TRUE;
  handle_attrs.lpSecurityDescriptor = NULL;

  /* create a pipe for child's stdin.  Don't allow child to */
  /* inherit the write end of the pipe.                     */
  CreatePipe (&hpStdInput[0], &hpStdInput[1], &handle_attrs, 0);
  SetHandleInformation (hpStdInput[1], HANDLE_FLAG_INHERIT, 0);

  /* create a pipe for child's stdout.  Don't allow child to */
  /* inherit the read end of the pipe.                       */
  CreatePipe (&hpStdOutput[0], &hpStdOutput[1], &handle_attrs, 0);
  SetHandleInformation (hpStdOutput[0], HANDLE_FLAG_INHERIT, 0);

  psi->hStdInput = hpStdInput[0];
  psi->hStdOutput = hpStdOutput[1];
  psi->hStdError = hpStdOutput[1];

  *hpToChild = hpStdInput[1];
  *hpFromChild = hpStdOutput[0];
  *hpToParent = hpStdOutput[1];
  *hpFromParent = hpStdInput[0];

  debugMsg (2, "Set up pipes for child stdio");
  return TRUE;
}

DWORD
run2_start_child (char *cmdline, char *startin, int wait_for_child)
{
  STARTUPINFO start;
  SECURITY_ATTRIBUTES sec_attrs;
  PROCESS_INFORMATION child;
  DWORD retval = 0;
  BOOL bFuncReturn;
  BOOL bHaveConsole;
  BOOL bUsingPipes;
  BOOL bForceUsingPipes = FALSE;
  HANDLE hToChild, hFromChild;
  HANDLE hToParent, hFromParent;
  BOOL bUseMessageOnlyWorkaround = FALSE;
  BOOL WINAPI (*AttachConsoleFP)(DWORD) = NULL;
  HWND WINAPI (*GetConsoleWindowFP)(VOID) = NULL;
  DWORD vers;
  DWORD os_version;

  run2_setup_win_environ ();
  bHaveConsole = run2_have_console ();

  /* Need this to work around a bug in Windows 7 */
  vers = GetVersion ();
  os_version = (LOBYTE (LOWORD (vers)) << 8) | HIBYTE (LOWORD (vers));
  bUseMessageOnlyWorkaround = (os_version >= 0x0601);

  if (bUseMessageOnlyWorkaround)
    {
      HMODULE lib = GetModuleHandle ("kernel32.dll");
      AttachConsoleFP = (BOOL WINAPI (*)(DWORD))
        GetProcAddress (lib, "AttachConsole");
      GetConsoleWindowFP = (HWND WINAPI (*)(VOID))
        GetProcAddress (lib, "GetConsoleWindow");
      if (!AttachConsoleFP || !GetConsoleWindowFP)
          os_version = 0;
    }

#ifdef DEBUG_FORCE_PIPES
  bForceUsingPipes = TRUE;
  FreeConsole();
  bHaveConsole = FALSE;
#else
   if (bUseMessageOnlyWorkaround)
     {
       if (!bHaveConsole)
         {
           AllocConsole ();
           bHaveConsole = TRUE;
           SetParent ((*GetConsoleWindowFP) (), HWND_MESSAGE);
         }
     }
   else if (!bHaveConsole)
     {
       bHaveConsole = run2_setup_invisible_console ();
     }
#endif

  if (!run2_configure_startupinfo (&start, bHaveConsole,
                                   bForceUsingPipes, &bUsingPipes,
                                   &hToChild, &hFromChild,
                                   &hToParent, &hFromParent))
    {
      fatalMsg ("Error attempting to start %s", cmdline);
      return -1;
    }

  sec_attrs.nLength = sizeof (sec_attrs);
  sec_attrs.lpSecurityDescriptor = NULL;
  sec_attrs.bInheritHandle = TRUE;

  ZeroMemory (&child, sizeof (PROCESS_INFORMATION));
  bFuncReturn = CreateProcess (NULL,       /* LPCTSTR lpApplicationName */
                               cmdline,    /* LPTSTR lpCommandLine */
                               &sec_attrs, /* LPSECURITY_ATTRIBUTES lpProcessAttributes */
                               NULL,       /* LPSECURITY_ATTRIBUTES lpThreadAttributes */
                               TRUE,       /* BOOL bInheritHandles */
                               0,          /* DWORD dwCreationFlags */
                               NULL,       /* LPVOID lpEnvironment (use parent's env) */
                               startin,    /* LPCTSTR lpCurrentDirectory */
                               &start,     /* LPSTARTUPINFO lpStartupInfo */
                               &child);    /* LPPROCESS_INFORMATION lpProcessInformation */

  if (bUsingPipes)
    {
      CloseHandle (hFromParent);
      CloseHandle (hToParent);
    }

  if (bFuncReturn)
    {
      if (wait_for_child)
        {
          if (bUsingPipes)
            {
              HANDLE handles[2] = { child.hProcess, hFromChild };
              COMMTIMEOUTS timeouts;

              /* only read bytes that are ready, and return immediately */
              GetCommTimeouts (hFromChild, &timeouts);
              timeouts.ReadIntervalTimeout = MAXDWORD;
              timeouts.ReadTotalTimeoutMultiplier = 0;
              timeouts.ReadTotalTimeoutConstant = 0;
              SetCommTimeouts (hFromChild, &timeouts);

              while (WaitForMultipleObjects (2, handles, FALSE, INFINITE) ==
                     WAIT_OBJECT_0 + 1)
                {
                  char buffer[1024];
                  DWORD dwRead;
                  ReadFile (hFromChild, buffer, 1024, &dwRead, NULL);
                }
            }
          else
            {
              WaitForSingleObject (child.hProcess, INFINITE);
            }
          GetExitCodeProcess (child.hProcess, &retval);
        }
      CloseHandle (child.hThread);
      CloseHandle (child.hProcess);
      if (bUsingPipes)
        {
          CloseHandle (hFromChild);
          CloseHandle (hToChild);
        }
    }
  else
    {
      if (bUsingPipes)
        {
          CloseHandle (hFromChild);
          CloseHandle (hToChild);
        }
      fatalMsg ("Could not start %s", cmdline);
      return -1;
    }
  return retval;
}

